package figure8;

import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.Line;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.Mixer;
import javax.sound.sampled.UnsupportedAudioFileException;

import figure8.GUI.DrumMachineTrackPanel;
import figure8.GUI.TrackPanel;
import figure8.Network.Client;
import figure8.Network.Message;
import figure8.Network.Message.Type;
import figure8.Network.NetworkConnection;

public class DrumMachineTrack extends BaseTrack {
	private Mixer mixer;
	private File[] drums; 
	private File[] drumKits; 
	private File folder;
	private static DataLine.Info info;
	private static AudioFormat format;
	private WAVTrack[] samples;
	private LoopTiming timing;
	private int drumKit;
	private Boolean[][] drumMachine; 
	private boolean stop, muted;
	
	public static DrumMachineTrack fromString(Project project, String serialized) {
		DrumMachineTrack track = new DrumMachineTrack(project.getTiming());
		track.deserializeMetadata(serialized);
		
		return track;
	}
	
	public DrumMachineTrack (LoopTiming timing) {
		super("Unnamed DrumTrack", false, false, 0, 0);
		
		Pattern pattern = null;
		Matcher matcher;
		int mixerSelector = 0;
		int count = 0;
		for ( Mixer.Info mix : Rec.getOutputMixers())
		{
			pattern = Pattern.compile("Java Sound Audio Engine");
			matcher = pattern.matcher(mix.toString());
			if (matcher.find()){
				mixerSelector = count;
			}
			count++;
		}
		mixer = AudioSystem.getMixer(Rec.getOutputMixers()[mixerSelector]);
		DrumFilter filter = new DrumFilter();
		drumKits = (new File ("DrumSamples/")).listFiles(filter);
		drums = (new File ("DrumSamples/Default/")).listFiles(filter);
		Arrays.sort(drumKits);
		Arrays.sort(drums);
		drumKit = 0;
		this.timing = timing;
		volume = 0;
		stop = false;
		muted = false;
		float sampleRate = 44100.0F;
		// 8000,11025,16000,22050,44100
		int sampleSizeInBits = 16;
		// 8,16
		int channels = 1;
		// 1,2
		boolean signed = true;
		// true,false
		boolean bigEndian = false;
		// true,false
		format = new AudioFormat(sampleRate, sampleSizeInBits, channels,
				signed, bigEndian);
		info = new DataLine.Info(Clip.class, format);
		//(Clip) mixerPlay.getLine(info)
		//samples = new WAVTrack[drums.length];
		/*for (int i = 0; i < drums.length; i++) {
			try {
				samples[i] = new WAVTrack ((Clip) mixer.getLine(info), drums[i], this.timing);
			} catch (LineUnavailableException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			} catch (UnsupportedAudioFileException e) {
				e.printStackTrace();
			}
		}*/
		drumMachine = new Boolean [this.timing.getBeats() * 4][drums.length];
		for (int i = 0; i < this.timing.getBeats() * 4; i++) {
			for (int j = 0; j < drums.length; j++) {
				if (i == 0 && j == 3) drumMachine[i][j] = true;
				else if (i > 0 && i % 4 == 0 && j == 9) drumMachine[i][j] = true;
				else drumMachine[i][j] = false;
			}
		}
		changeKit(drumKit);
	}
	
	//Constructor when loading DrumMachine from a file
	public DrumMachineTrack (LoopTiming timing, Boolean[][] drum) {
		super("Unnamed DrumTrack", false, false, 0, 0);
		
		Pattern pattern = null;
		Matcher matcher;
		int mixerSelector = 0;
		int count = 0;
		for ( Mixer.Info mix : Rec.getOutputMixers())
		{
			pattern = Pattern.compile("Java Sound Audio Engine");
			matcher = pattern.matcher(mix.toString());
			if (matcher.find()){
				mixerSelector = count;
			}
			count++;
		}
		mixer = AudioSystem.getMixer(Rec.getOutputMixers()[mixerSelector]);
		drumKits = (new File ("DrumSamples/")).listFiles(new DrumFilter());
		//read from file and get the value of drumkit then assign it to drumkit
		//using 1 for now to get default kit
		drums = drumKits[drumKit].listFiles(new DrumFilter());
		Arrays.sort(drumKits);
		Arrays.sort(drums);
		drumKit = 0;
		this.timing = timing;
		volume = 0;
		stop = false;
		muted = false;
		float sampleRate = 44100.0F;
		// 8000,11025,16000,22050,44100
		int sampleSizeInBits = 16;
		// 8,16
		int channels = 1;
		// 1,2
		boolean signed = true;
		// true,false
		boolean bigEndian = false;
		// true,false
		format = new AudioFormat(sampleRate, sampleSizeInBits, channels,
				signed, bigEndian);
		info = new DataLine.Info(Clip.class, format);
		/*samples = new WAVTrack[drums.length - 1];
		for (int i = 1; i < drums.length; i++) {
			try {
				samples[i - 1] = new WAVTrack ((Clip) mixer.getLine(info), drums[i], this.timing);
			} catch (LineUnavailableException e) {
				e.printStackTrace();
			}
		}*/
		changeKit(drumKit);
		drumMachine = drum;
	}
	
	public void changeKit (int kit) {
		if(kit != drumKit || samples == null) {
			drums = drumKits[kit].listFiles(new DrumFilter());
			Arrays.sort(drums);
			drumKit = kit;
			samples = new WAVTrack[drums.length];
			for (int i = 0; i < drums.length; i++) {
				try {
					samples[i] = new WAVTrack ((Clip) mixer.getLine(info), drums[i], this.timing);
				} catch (LineUnavailableException e) {
					e.printStackTrace();
				} catch (IOException e) {
					e.printStackTrace();
				} catch (UnsupportedAudioFileException e) {
					e.printStackTrace();
				}
			}
		}
	}
	
	public int getKit() {
		return drumKit;
	}
	
	public File[] samples () {
		return drums;
	}
	
	@Override
	public void play() {
	}
	
	public void playInThread() {
		Thread playDrums = new PlayBack();
		playDrums.start();
	}
	
	public void switchProject (LoopTiming timing, Boolean drumProgram[][]) {
		this.timing = timing;
		drumMachine = drumProgram;
	}
	
	public void switchTiming (LoopTiming timing) {
		this.timing = timing;
		drumMachine = new Boolean [this.timing.getBeats() * 4][drums.length];
		for (int i = 0; i < this.timing.getBeats() * 4; i++) {
			for (int j = 0; j < drums.length; j++) {
				if (i == 0 && j == 3) drumMachine[i][j] = true;
				else if (i > 0 && i % 4 == 0 && j == 9) drumMachine[i][j] = true;
				else drumMachine[i][j] = false;
			}
		}
	}
	
	public void playBack() {
		stop = false;
		for (int x = 0; x < this.timing.getBars(); x++) {
			for (int i = 0; i < this.timing.getBeats() * 4; i++) {
				if (!stop) {
					for (int j = 0; j < drums.length; j++) {
						if (drumMachine[i][j]) {
							//samples[j].stop();
							samples[j].play();
						}
					}
					try {
						Thread.sleep((int)(this.timing.getBeatLength()/4));
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		}
	}
	
	public void countIn() {
		stop = false;
		for (int i = 0; i < this.timing.getBeats() * 4; i++) {
			if (!stop) {
				for (int j = 0; j < drums.length; j++) {
					if (drumMachine[i][j]) samples[j].play();
				}
				try {
					Thread.sleep((int)(this.timing.getBeatLength()/(double)4));
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}
	
	class PlayBack extends Thread {
		public void run() {
			stop = false;
			for (int x = 0; x < timing.getBars(); x++) {
				for (int i = 0; i < timing.getBeats() * 4; i++) {
					if (!stop) {
						for (int j = 0; j < drums.length; j++) {
							if (drumMachine[i][j]) {
								//samples[j].stop();
								samples[j].play();
							}
						}
						try {
							Thread.sleep((int)(timing.getBeatLength()/4));
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
					}
				}
			}
		}
	}
	
	public boolean isPressed (int beat, int drum) {
		return drumMachine[beat][drum];
	}
	
	@Override
	public void pause() {
		
	}

	@Override
	public void stop() {
		stop = true;
	}
	
	public void drumChange(int beat, int drum) {
		if (drumMachine[beat][drum]) drumMachine[beat][drum] = false;
		else drumMachine[beat][drum] = true;
	}

	@Override
	public void setMuted(boolean newMuted) {
		muted = newMuted;
		for (int i = 0; i < samples.length; i++) {
			samples[i].setMuted(muted);
		}
		
	}
	
	@Override
	public boolean getMuted() {
		return muted;
		
	}
	
	
	public void closeLines() {
		for(int i = 0; i<samples.length; i++) {
			samples[i].close();
		}
	}

	@Override
	public void setVolume(float newVolume) {
		volume = newVolume;
		for (int i = 0; i < samples.length; i++) {
			samples[i].setVolume(volume);
		}
	}

	@Override
	public TrackPanel makeTrackPanel(Controller controller) {
		return new DrumMachineTrackPanel(controller, drums, this);
	}

	@Override
	public File getFile() {
		return null;
	}
	
	public Boolean[][] getDrumMachine() {
		return drumMachine;
	}
	
	public int getNumberBeats() {
		return this.timing.getBeats() * 4;
	}

	@Override
	public void send(NetworkConnection client, Client exclude) {
		System.out.println("Sending DrumMachineTrack " + this.getName());
		
		String data = "DRUM " + this.serializeMetadata();
		
		client.send(new Message(Message.Type.TRACK_ADD, data));
	}
	
	@Override
	public void sendMeta(NetworkConnection client) {
		System.out.println("Sending DrumMachineTrack " + this.getName());
		
		String data = "DRUM " + this.serializeMetadata();
		
		client.send(new Message(Message.Type.TRACK_UPDATE, data));
	}
	
	@Override
	public void updateMeta(String data) {
		this.deserializeMetadata(data);
		this.fireTrackChangedEvent(false);
	}
	
    @Override
    public String serializeMetadata() {
    	String grid = this.drumMachine[0].length + " ";
    	
    	for (Boolean row[] : this.drumMachine) {
    		for (Boolean on : row) {
    			if (on) {
    				grid += "1";
    			} else {
    				grid += "0";
    			}
    		}
    	}
    	
        return super.serializeMetadata() + " " + grid + " " + Integer.toString(this.drumKit);
    }

    public String deserializeMetadata(String metadata) {
        String[] parts = super.deserializeMetadata(metadata).split(" ", 4);
        
        int cols = Integer.parseInt(parts[0]);
        int col = 0;
        int row = 0;
        
        for (char on : parts[1].toCharArray()) {
        	this.drumMachine[row][col] = (on == '1') ? true : false;
        	
        	col++;
        	
        	if (col >= cols) {
        		col = 0;
        		row++;
        	}
        }
        
        int kit = Integer.parseInt(parts[2]);
        
        if (kit != this.drumKit) {
        	this.changeKit(kit);
        }
        
        String leftover;
        
        if (parts.length < 4) {
                leftover = "";
        } else {
                leftover = parts[3];
        }
        
        return leftover;
    }
	
	class DrumFilter implements FilenameFilter {
	    public boolean accept(File dir, String name) {
	        return !(name.equals(".svn"));
	    }
	}

	@Override
	public Type getType() {
		return Type.DRUM;
	}

	@Override
	public void close() {
		// TODO Auto-generated method stub
		
	}
	
	@Override
	public boolean ready() {
		return true;
	}
	
	@Override
	public boolean isDrum() {
		return true;
	}
}
